/*-
 *   BSD LICENSE
 *
 *   Copyright (c) Intel Corporation.
 *   All rights reserved.
 *
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *     * Neither the name of Intel Corporation nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <errno.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <ifaddrs.h>
#include <linux/if_packet.h>

#include "nvmf.h"
#include "request.h"
#include "subsystem.h"
#include "transport.h"
#include "nvmf_spec.h"
#include "session.h"

#include "core.h"
#include "dbg.h"
#include "sysy_lib.h"

#define lich_nvmf_min(a, b) (((a)<(b))?(a):(b))
#define lich_nvmf_max(a, b) (((a)>(b))?(a):(b))
#define LICH_NVMF_CONFIG_QUEUES_PER_SESSION_DEFAULT 4
#define LICH_NVMF_CONFIG_QUEUES_PER_SESSION_MIN 2
#define LICH_NVMF_CONFIG_QUEUES_PER_SESSION_MAX 1024
#define LICH_NVMF_CONFIG_QUEUE_DEPTH_DEFAULT 128
#define LICH_NVMF_CONFIG_QUEUE_DEPTH_MIN 16
#define LICH_NVMF_CONFIG_QUEUE_DEPTH_MAX 1024
#define LICH_NVMF_CONFIG_IN_CAPSULE_DATA_SIZE_DEFAULT 4096
#define LICH_NVMF_CONFIG_IN_CAPSULE_DATA_SIZE_MIN 4096
#define LICH_NVMF_CONFIG_IN_CAPSULE_DATA_SIZE_MAX 131072
#define LICH_NVMF_CONFIG_MAX_IO_SIZE_DEFAULT 131072
#define LICH_NVMF_CONFIG_MAX_IO_SIZE_MIN 4096
#define LICH_NVMF_CONFIG_MAX_IO_SIZE_MAX 131072

#define NQN "nqn.2016-06.fusionstor.lich"
#define SPDK_NVMF_DISCOVERY_NQN "nqn.2014-08.org.nvmexpress.discovery"

struct lich_nvmf_globals g_nvmf_tgt;

static int nvmf_tgt_init()
{
        int ret;

        DINFO("Nvmf Acceptor Polling Initiator ....\n");

        g_nvmf_tgt.max_queues_per_session = LICH_NVMF_CONFIG_QUEUES_PER_SESSION_DEFAULT;
        g_nvmf_tgt.max_queue_depth = LICH_NVMF_CONFIG_QUEUE_DEPTH_DEFAULT;
        g_nvmf_tgt.in_capsule_data_size = LICH_NVMF_CONFIG_IN_CAPSULE_DATA_SIZE_DEFAULT;
        g_nvmf_tgt.max_io_size = LICH_NVMF_CONFIG_MAX_IO_SIZE_DEFAULT;

        /*init rdma*/
        ret = lich_nvmf_transport_init();
        if (unlikely(ret <= 0)) {
                DERROR("Nvmf Transport initialization failed !!!\n");
                ret = EPERM;
                GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

static int __lich_nvmf_handle_connect__(va_list ap)
{
        struct lich_nvmf_request *req = va_arg(ap, struct lich_nvmf_request *);
        va_end(ap);

        lich_nvmf_handle_connect(req);

        return 0;
}

static void connect_cb(void *cb_ctx, struct lich_nvmf_request *req)
{
        struct lich_nvmf_subsystem *subsystem = cb_ctx;

        core_request(subsystem->lcore, -1, "nvmf_rdma_connect",
                        __lich_nvmf_handle_connect__, req, subsystem);
}

static void discovery_connect_cb(void *cb_ctx, struct lich_nvmf_request *req)
{
        (void)cb_ctx;

        core_map_update();

        lich_nvmf_handle_connect(req);
}

static int __lich_nvmf_handle_disconnect__(va_list ap)
{
        struct lich_nvmf_conn *conn = va_arg(ap, struct lich_nvmf_conn *);
        va_end(ap);

        lich_nvmf_session_disconnect(conn);

        return 0;
}

static void disconnect_cb(void *cb_ctx, struct lich_nvmf_conn *conn)
{
        struct lich_nvmf_subsystem *subsystem = cb_ctx;

        core_request(subsystem->lcore, -1, "nvmf_rdma_disconnect",
                        __lich_nvmf_handle_disconnect__, conn);
}

static void discovery_disconnect_cb(void *cb_ctx, struct lich_nvmf_conn *conn)
{
        (void)cb_ctx;

        lich_nvmf_session_disconnect(conn);
}

void lich_nvmf_subsystem_listen_all(struct lich_nvmf_subsystem *subsystem, char *listen_port)
{
        struct ifaddrs *ifaddr, *ifa;
        int family;
        struct sockaddr_in *in_addr;

        if (getifaddrs(&ifaddr) == -1) {
                //perror("getifaddrs");
                return;
        }

        for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
                if (ifa->ifa_addr == NULL)
                        continue;

                family = ifa->ifa_addr->sa_family;

                in_addr = (struct sockaddr_in *)ifa->ifa_addr;

                if(family == AF_INET) {
                        lich_nvmf_subsystem_add_listener(subsystem, "RDMA", in_addr->sin_addr.s_addr, listen_port);
                }
        }

        freeifaddrs(ifaddr);
}

static int init_nvmf_discovery_subsystem(void)
{
        int ret, core_hash = -1;
        struct lich_nvmf_subsystem *subsystem;
        char *listen_port = malloc(8);

        snprintf(listen_port, 8, "%d", NVMF_LISTEN_PORT);

        subsystem = lich_nvmf_create_subsystem(core_hash, SPDK_NVMF_DISCOVERY_NQN,
                        SPDK_NVMF_SUBTYPE_DISCOVERY, NVMF_SUBSYSTEM_MODE_DIRECT,
                        NULL, discovery_connect_cb, discovery_disconnect_cb);
        if (unlikely(subsystem == NULL)) {
                DERROR("discovery subsystem create failed !!!\n");
                ret = ENOMEM;
                GOTO(err_free, ret);
        }

        //lich_nvmf_subsystem_add_listener(subsystem, "RDMA", INADDR_ANY, listen_port);
        lich_nvmf_subsystem_listen_all(subsystem, listen_port);

        free(listen_port);
        return 0;
err_free:
        free(listen_port);
        return ret;
}

static void *nvmf_misc_worker(void *arg)
{
        int ret;
        (void)arg;

        ret = init_nvmf_discovery_subsystem();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = lich_nvmf_transport_epoll_add();
        if(unlikely(ret == 0)) {
                ret = ENONET;
                GOTO(err_ret, ret);
        }

        DINFO("Nvmf Acceptor Polling Running ....\n");
        while (1) {
                lich_nvmf_acceptor_poll();
        }

        return NULL;
err_ret:
        DERROR("nvmf misc thread error %d !!!\n",ret);
        return NULL;
}

int nvmf_core_subsystem_init(core_t *core)
{
        struct lich_nvmf_subsystem  *subsystem;
        char nqn[256];
        char *listen_port = malloc(8);

        snprintf(nqn, 256, "%s%d", NQN, core->hash + 1);
        snprintf(listen_port, 8, "%d", NVMF_LISTEN_PORT + core->hash + 1);
        DINFO("NVMF Subsystem NQN: %s, port: %s.\n", nqn, listen_port);

        subsystem = lich_nvmf_create_subsystem(core->hash, nqn,
                        SPDK_NVMF_SUBTYPE_NVME, NVMF_SUBSYSTEM_MODE_VIRTUAL,
                        NULL, connect_cb, disconnect_cb);
        if (unlikely(subsystem == NULL)) {
                DERROR("discovery subsystem create failed !!!\n");
                free((void *)listen_port);
                return ENOMEM;
        }

        //lich_nvmf_subsystem_add_listener(subsystem, "RDMA", INADDR_ANY, listen_port);
        lich_nvmf_subsystem_listen_all(subsystem, listen_port);
        core->subsystem = subsystem;
        free((void *)listen_port);

        return 0;
}

int lich_nvmf_tgt_init(void)
{
        int ret;

        ret = nvmf_tgt_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        /*create discovery and acceptor thread*/
        ret = sy_thread_create(nvmf_misc_worker, NULL);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}

int lich_nvmf_tgt_fini(void)
{
        lich_nvmf_transport_fini();

        return 0;
}

